#include "Mutagen.h"
#include "MainWindow.h"
#include "ContinuousSequencer.h"
#include "QDebug"
#include "Global.h"

Mutagen::Mutagen(QSemaphore* wait4CleanupDone) :
    w(new MainWindow()),
    extOSCThread(32244),
    timer(new MutaTimer(5)),
    frameTimer(new MutaTimer(40)),
    transport(new MutaTransport(w,w->geometry().width()*0.005,w->geometry().height()*0.006)),
    wait4CleanupDone(wait4CleanupDone),
    oscthulhuPort(32242)
{

    w->show();
    sequencers.append(new ContinuousSequencer(w,w->geometry().height() * 0.05));

    connect(w,SIGNAL(resized()),transport,SLOT(windowResized()));
    connect(w,SIGNAL(resized()),sequencers.at(0),SLOT(windowResized()));

    connect(sequencers.at(0),SIGNAL(attack(QString,QString,float)),&extOSCThread,SLOT(attack(QString,QString,float)));

    connect(sequencers.at(0),SIGNAL(release(QString,QString,float)),&extOSCThread,SLOT(release(QString,QString,float)));

    connect(sequencers.at(0),SIGNAL(sustain(QString,QString,float)),&extOSCThread,SLOT(sustain(QString,QString,float)));

    connect(sequencers.at(0)->getView(),SIGNAL(zoomChanged(float)),this,SLOT(zoomChanged(float)));

    connect(sequencers.at(0)->getView(),SIGNAL(scrolling(int,int)),this,SLOT(scrollContents(int,int)));

    connect(timer, SIGNAL(timeout(qreal)), sequencers.at(0), SLOT(mutaTimeEvent(qreal)));

    connect(frameTimer,SIGNAL(timeout(qreal)),sequencers.at(0)->getView(),SLOT(updateCursor(qreal)));

    connect(transport->getLineButton(),SIGNAL(clicked()),sequencers.at(0),SLOT(lineEditing()));

    connect(transport->getEventButton(),SIGNAL(clicked()),sequencers.at(0),SLOT(eventEditing()));

    connect(transport->getRepeatButton(),SIGNAL(clicked()),sequencers.at(0),SLOT(repeatEditing()));

    connect(transport->getCurveButton(),SIGNAL(clicked()),sequencers.at(0),SLOT(curveEditing()));

    connect(transport->getTendrilButton(),SIGNAL(clicked()),sequencers.at(0),SLOT(tendrilEditing()));

    connect(transport->getBlendButton(),SIGNAL(clicked()),sequencers.at(0),SLOT(blendEditing()));

    connect(transport->getStrangeAttractorButton(),SIGNAL(clicked()),sequencers.at(0),SLOT(strangeAttractorEditing()));

    connect(transport->getPlay(),SIGNAL(pressed()),frameTimer,SLOT(startPlaying()));
    connect(transport->getPlay(),SIGNAL(pressed()),timer,SLOT(startPlaying()));

    connect(transport->getStop(),SIGNAL(pressed()),frameTimer,SLOT(stopPlaying()));
    connect(transport->getStop(),SIGNAL(pressed()),timer,SLOT(stopPlaying()));
    connect(transport->getStop(),SIGNAL(pressed()),sequencers.at(0),SLOT(stopAllNotes()));

    connect(transport->getRewind(),SIGNAL(pressed()),sequencers.at(0)->getCursor(),SLOT(reset()));

    connect(transport->getRewind(),SIGNAL(pressed()),sequencers.at(0)->getView(),SLOT(update()));

    connect(transport->getRewind(),SIGNAL(pressed()),sequencers.at(0),SLOT(stopAllNotes()));

    connect(transport->getRewind(),SIGNAL(pressed()),frameTimer,SLOT(stopPlaying()));
    connect(transport->getRewind(),SIGNAL(pressed()),timer,SLOT(stopPlaying()));

    connect(transport->getSave(),SIGNAL(pressed()),this,SLOT(save()));
    connect(transport->getLoad(),SIGNAL(pressed()),this,SLOT(load()));

    connect(transport->getBack(),SIGNAL(pressed()),sequencers.at(0),SLOT(decrementMarker()));
    connect(transport->getForward(),SIGNAL(pressed()),sequencers.at(0),SLOT(incrementMarker()));

    connect(this,SIGNAL(quitting()),sequencers.at(0),SLOT(stopAllNotes()));

    connect(transport->getBeatsDisplay(),SIGNAL(valueChanged(int)),sequencers.at(0)->getView(),SLOT(setBeats(int)));

    connect(transport->getBeatsDisplay(),SIGNAL(valueChanged(int)),sequencers.at(0)->getCursor(),SLOT(setMeterBeats(int)));
    connect(transport->getLengthDisplay(),SIGNAL(valueChanged(int)),sequencers.at(0)->getCursor(),SLOT(setMeterLength(int)));

    connect(transport->getTempoDisplay(),SIGNAL(valueChanged(double)),sequencers.at(0)->getCursor(),SLOT(setTempo(double)));

    connect(transport->getSendPort(),SIGNAL(valueChanged(int)),&extOSCThread,SLOT(changeSendPort(int)));

    //OSCthulhu Send
    connect(transport->getOSCthulhuButton(),SIGNAL(pressed()),this,SLOT(connectOSCthulhu()));
    connect(sequencers.at(0)->getView(),SIGNAL(addEvent(float,float,int,float)),&extOSCThread,SLOT(addEvent(float,float,int,float)));
    connect(sequencers.at(0)->getView(),SIGNAL(addLine(QString,float,float,int)),&extOSCThread,SLOT(addLine(QString,float,float,int)));
    connect(sequencers.at(0)->getView(),SIGNAL(removeObject(QString)),&extOSCThread,SLOT(removeObject(QString)));
    connect(sequencers.at(0)->getView(),SIGNAL(addedMutaEventToScene(MutaEvent*)),this,SLOT(addedMutaEventToScene(MutaEvent*)));
    connect(sequencers.at(0)->getView(),SIGNAL(addedMutaLineToScene(MutaLine*)),this,SLOT(addedMutaLineToScene(MutaLine*)));
    connect(sequencers.at(0)->getView(),SIGNAL(addedMutaCurveToScene(MutaCurve*)),this,SLOT(addedMutaCurveToScene(MutaCurve*)));
    connect(sequencers.at(0)->getView(),SIGNAL(addedMutaTendrilToScene(MutaTendril*)),this,SLOT(addedMutaTendrilToScene(MutaTendril*)));
    connect(sequencers.at(0)->getView(),SIGNAL(addedMutaRepeatToScene(MutaRepeat*)),this,SLOT(addedMutaRepeatToScene(MutaRepeat*)));
    connect(sequencers.at(0)->getView(),SIGNAL(cursorPositionChanged(QString,QPointF)),&extOSCThread,SLOT(posChange(QString,QPointF)));
    connect(extOSCThread.getListener(),SIGNAL(loggedIn()),&extOSCThread,SLOT(addUserCursor()));

    //OSCthulhu Receive
    connect(extOSCThread.getListener(),SIGNAL(addMutaEvent(QString,QString,float,float,int,float)),sequencers.at(0)->getView(),SLOT(addMutaEvent(QString,QString,float,float,int,float)));
    connect(extOSCThread.getListener(),SIGNAL(addMutaLine(QString,QString,QString,float,float,int,QString)),sequencers.at(0)->getView(),SLOT(addMutaLine(QString,QString,QString,float,float,int,QString)));
    connect(extOSCThread.getListener(),SIGNAL(removeMutaObject(QString)),sequencers.at(0)->getView(),SLOT(removeMutaObject(QString)));
    connect(extOSCThread.getListener(),SIGNAL(setMutaX(QString,float)),sequencers.at(0)->getView(),SLOT(setMutaX(QString,float)));
    connect(extOSCThread.getListener(),SIGNAL(setMutaY(QString,float)),sequencers.at(0)->getView(),SLOT(setMutaY(QString,float)));
    connect(extOSCThread.getListener(),SIGNAL(setControlPoints(QString,QString)),sequencers.at(0)->getView(),SLOT(setControlPoints(QString,QString)));
    //connect(extOSCThread.getListener(),SIGNAL(userAdded(QString)),sequencers.at(0)->getView(),SLOT(userAdded(QString)));
    //connect(extOSCThread.getListener(),SIGNAL(userRemoved(QString)),sequencers.at(0)->getView(),SLOT(userRemoved(QString)));
    connect(extOSCThread.getListener(),SIGNAL(addUserCursor(QString,QString)),sequencers.at(0)->getView(),SLOT(addUserCursor(QString,QString)));

    connect(extOSCThread.getListener(),SIGNAL(startAllNotes()),sequencers.at(0),SLOT(startAllNotes()));

    frameTimer->start(QThread::LowPriority);
    timer->start(QThread::TimeCriticalPriority);

    sequencers.at(0)->setParent(timer);
    extOSCThread.setParent(timer);
}
Mutagen::~Mutagen()
{
    extOSCThread.quitConnection();
}

void Mutagen::sendOSC(osc::OutboundPacketStream p) {
    extOSCThread.send(
        "127.0.0.1",
        57120,
        p
    );
}

void Mutagen::zoomChanged(float zoom)
{
    for(int i=0;i<sequencers.size();i++) {
        sequencers.at(i)->getView()->setTransform(QTransform().scale(zoom,zoom));
    }
}

void Mutagen::scrollContents(int dx, int dy)
{
    for(int i=0;i<sequencers.size();i++) {
        sequencers.at(i)->getView()->multiScrollContentsBy(dx,dy);
    }
}

//FIX LOADING AND SAVING
void Mutagen::save()
{
    QString properties;

    //Send Port
    properties.append("port ");
    properties.append(QString().number(transport->getSendPort()->value()));
    properties.append("/x/ ");

    //Tempo
    properties.append("tempo ");
    properties.append(QString().number(transport->getTempoDisplay()->value()));
    properties.append("/x/ ");

    //Beats
    properties.append("beats ");
    properties.append(QString().number(transport->getBeatsDisplay()->value()));
    properties.append("/x/ ");

    //Length
    properties.append("length ");
    properties.append(QString().number(transport->getLengthDisplay()->value()));
    properties.append("/x/ ");

    foreach(QGraphicsItem* cItem,sequencers.at(0)->getScene()->items()) {
        int type = cItem->type();
        if(type == 100 || type == 101 || type ==105 || type ==106) {
            properties.append( ((MutaLine*)cItem)->getProperties().join(" ") );
            properties.append("/x/ ");
        }
    }
    qDebug() << properties;
    QString fileName = QFileDialog::getSaveFileName(w,
             tr("Save Mutagen session"), "",
             tr("Mutagen Session (*.mus);;All Files (*)"));
    qDebug() << fileName;

    if (fileName.isEmpty()) {
             return;
    } else {
        QFile file(fileName);
        if (!file.open(QIODevice::WriteOnly)) {
            QMessageBox::information(w, tr("Unable to open file"),
            file.errorString());
            return;
        }
        QDataStream out(&file);
        out.setVersion(QDataStream::Qt_4_5);
        out << properties;
    }
}

void Mutagen::load()
{
    QString properties;
    QString fileName = QFileDialog::getOpenFileName(w,
            tr("Open Mutagen Session"), "",
            tr("Mutagen Session (*.mus);;All Files (*)"));
    if (fileName.isEmpty()) {
             return;
    } else {
        QFile file(fileName);

        if (!file.open(QIODevice::ReadOnly)) {
            QMessageBox::information(w, tr("Unable to open file"),
                file.errorString());
                 return;
        }

        QDataStream in(&file);
        in.setVersion(QDataStream::Qt_4_5);
        in >> properties;
    }
    //qDebug() << properties;
    QStringList listProperties = properties.split("/x/ ");
    qDebug() << listProperties;
    foreach(QString string,listProperties) {
        QStringList list = string.split(" ");

        if(list.at(0).compare("me") == 0) {
            MutaEvent* event = new MutaEvent(Muta::getObjectID(),list.at(1),list.at(2).toFloat(),list.at(3).toFloat(),list.at(4).toInt());
            event->changeLength(list.at(5).toFloat());
            sequencers.at(0)->getScene()->addItem(event);
        } else if(list.at(0).compare("ml") == 0) {
            MutaLine* line = new MutaLine(Muta::getObjectID(),list.at(1),QPointF(list.at(2).toFloat(),list.at(3).toFloat()),QPointF(list.at(7).toFloat(),list.at(8).toFloat()),list.at(4).toInt(),0);
            bool curveList = false;
            int curvePointNum = 0;
            for(int i=7;i<list.size();i++){
                if(list.at(i).compare("crv")==0) {
                    qDebug() << "crv point reached";
                    curveList = true;
                    //i++;
                } else {
                    if(curveList){
                        qDebug() << "moving curve point " << list.at(i).toFloat() << list.at(i+1).toFloat();
                        line->setCurvePoint(curvePointNum,QPointF(list.at(i).toFloat(),list.at(i+1).toFloat()));
                        curvePointNum++;
                        i=i+1;
                    } else {
                        qDebug() << "adding control point " << list.at(i).toFloat() << list.at(i+1).toFloat();
                        line->addControlPointNQ(QPointF(list.at(i).toFloat()+list.at(2).toFloat(),list.at(3).toFloat()+list.at(i+1).toFloat()));
                        i=i+1;

                    }
                }
            }
            sequencers.at(0)->getScene()->addItem(line);
        } else if(list.at(0).compare("mc") == 0) {
            MutaCurve* curve = new MutaCurve(Muta::getObjectID(),list.at(1),QPointF(list.at(2).toFloat(),list.at(3).toFloat()),QPointF(list.at(7).toFloat(),list.at(8).toFloat()),list.at(4).toInt(),0);
            for(int i=7;i<list.size();i++){
                qDebug() << "adding control point " << list.at(i).toFloat() << list.at(i+1).toFloat();
                curve->addControlPointNQ(QPointF(list.at(i).toFloat()+list.at(2).toFloat(),list.at(3).toFloat()+list.at(i+1).toFloat()));
                i=i+1;
            }
            sequencers.at(0)->getScene()->addItem(curve);
        } else if(list.at(0).compare("rt") == 0) {
            //connect(view,SIGNAL(repeatObjectAdded(MutaRepeat*)),this,SLOT(addRepeatMarker(MutaRepeat*)));
            MutaRepeat* repeat = new MutaRepeat("none",list.at(1).toFloat(),0,0);
            sequencers.at(0)->getScene()->addItem(repeat);
            sequencers.at(0)->addRepeatMarker(repeat);
        } else if(list.at(0).compare("port") == 0) {
            transport->getSendPort()->setValue(list.at(1).toFloat());
        } else if(list.at(0).compare("tempo") == 0) {
            transport->getTempoDisplay()->setValue(list.at(1).toFloat());
        } else if(list.at(0).compare("beats") == 0) {
            transport->getBeatsDisplay()->setValue(list.at(1).toFloat());
        } else if(list.at(0).compare("length") == 0) {
            transport->getLengthDisplay()->setValue(list.at(1).toFloat());
        }
        sequencers.at(0)->getView()->update();
    }
}
void Mutagen::handleQuit() {
    extOSCThread.quitConnection();
    emit quitting();
    qDebug() << "Client is shutting down...";
    wait4CleanupDone->release();
}

void Mutagen::connectOSCthulhu()
{
    Muta::ONLINE = true;
     qDebug() << "connecting to OSCthulhu" << Muta::ONLINE;
    extOSCThread.login();
}

void Mutagen::addedMutaEventToScene(MutaEvent *mutaEvent)
{
    connect(mutaEvent,SIGNAL(posChanged(QString,QPointF)),&extOSCThread,SLOT(posChange(QString,QPointF)));
}

void Mutagen::addedMutaLineToScene(MutaLine *mutaLine)
{
    connect(mutaLine,SIGNAL(posChanged(QString,QPointF)),&extOSCThread,SLOT(posChange(QString,QPointF)));
    connect(mutaLine,SIGNAL(controlPointAdded(QString,QString)),&extOSCThread,SLOT(controlPointAdded(QString,QString)));
}

void Mutagen::addedMutaCurveToScene(MutaCurve *mutaCurve)
{
    connect(mutaCurve,SIGNAL(posChanged(QString,QPointF)),&extOSCThread,SLOT(posChange(QString,QPointF)));
    connect(mutaCurve,SIGNAL(controlPointAdded(QString,QString)),&extOSCThread,SLOT(controlPointAdded(QString,QString)));
}

void Mutagen::addedMutaTendrilToScene(MutaTendril *mutaTendril)
{
    connect(mutaTendril,SIGNAL(posChanged(QString,QPointF)),&extOSCThread,SLOT(posChange(QString,QPointF)));
    connect(mutaTendril,SIGNAL(controlPointAdded(QString,QString)),&extOSCThread,SLOT(controlPointAdded(QString,QString)));
}

void Mutagen::addedMutaRepeatToScene(MutaRepeat *mutaRepeat)
{
    connect(mutaRepeat,SIGNAL(posChanged(QString,QPointF)),&extOSCThread,SLOT(posChange(QString,QPointF)));
}
